package repo

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
)

// InstallPlanItem 计划安装的项
type InstallPlanItem struct {
	Lable       *ModuleLabel    // 适合安装的项
	Views       map[string]bool // 适合的环境
	Usages      map[string]bool // 引入用途
	Description string          // 描述信息
}

// InstallPlan 安装计划
type InstallPlan struct {
	items        []*InstallPlanItem
	itemsMapping map[string]int
}

func (plan *InstallPlan) Merge(newItem *InstallPlanItem) {
	oldItemIndex, exist := plan.itemsMapping[newItem.Lable.String()]
	if !exist {
		plan.Add(newItem)
		return
	}

	oldItem := plan.items[oldItemIndex]

	// 合并 views
	for v := range newItem.Views {
		oldItem.Views[v] = true
	}

	// 合并 usages
	for u := range newItem.Usages {
		oldItem.Usages[u] = true
	}

	// 合并 Description
	if len(oldItem.Description) == 0 && len(newItem.Description) > 0 {
		oldItem.Description = newItem.Description
	}
}

func (plan *InstallPlan) Add(item *InstallPlanItem) bool {
	if _, exist := plan.itemsMapping[item.Lable.String()]; exist {
		return false
	}

	plan.itemsMapping[item.Lable.String()] = len(plan.items)
	plan.items = append(plan.items, item)
	return true
}

func (plan *InstallPlan) Find(label string) (*InstallPlanItem, bool) {
	itemIndex, exists := plan.itemsMapping[label]
	if !exists {
		return nil, false
	}

	return plan.items[itemIndex], true
}

func (plan *InstallPlan) Exists(label string) bool {
	_, exists := plan.itemsMapping[label]
	return exists
}

func (plan *InstallPlan) Count() int {
	return len(plan.items)
}

func (plan *InstallPlan) Item(index int) *InstallPlanItem {
	if index < 0 || index >= len(plan.items) {
		return nil
	}

	return plan.items[index]
}

func (plan *InstallPlan) Enumerate(handle func(index int, item *InstallPlanItem) bool) {
	for index, item := range plan.items {
		if !handle(index, item) {
			break
		}
	}
}

func NewInstallPlan(cap int) *InstallPlan {
	return &InstallPlan{
		items:        make([]*InstallPlanItem, 0, cap),
		itemsMapping: make(map[string]int),
	}
}

// ReadInstallPlanFile 用于加载整个安装计划
func ReadInstallPlanFile(planFile string, breakWhenInvalid bool) (*InstallPlan, error) {
	// 打开版本文件
	file, err := os.Open(planFile)
	if err != nil {
		return nil, fmt.Errorf("open install plan file `%v' failed: %v", planFile, err)
	}

	defer file.Close()

	return ReadInstallPlan(file, breakWhenInvalid)
}

func ReadInstallPlan(file io.Reader, breakWhenInvalid bool) (*InstallPlan, error) {
	plan := NewInstallPlan(100)

	// 创建一个行读取器
	reader := bufio.NewReader(file)
	lineno := 0
	for {
		// 读取一行
		text, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		}

		lineno++

		// 去掉行首尾空白
		line := string(bytes.TrimSpace(text))

		// 跳过空白行和注释行
		if (len(line) == 0) || (strings.HasPrefix(line, "#")) {
			continue
		}

		// 按空格拆分
		labelText, remain := Cut(line)
		envsText, remain := Cut(remain)
		usageText, descText := Cut(remain)

		// 被集成库：integration
		// 构建工具：build
		// 开发工具：development
		// 调研工具：research

		// 通过标签文本反向解析获得 ModuleLabel 对象
		label, err := ParseModuleLabel(labelText)
		if err != nil {
			if breakWhenInvalid {
				return nil, fmt.Errorf("invalid module label `%v' at line %v", err, lineno)
			}
			continue
		}

		// 分割环境视图(单纯一个横线可以表示该字段没有给值)
		views := map[string]bool{}
		if envsText != "-" && envsText != "" {
			views = SplitSet(envsText)
		}

		// 分割用途(单纯一个横线可以表示该字段没有给值)
		usages := map[string]bool{}
		if usageText != "-" && usageText != "" {
			usages = SplitSet(usageText)
		}

		// 保存安装计划项
		plan.Add(&InstallPlanItem{
			Lable:       label,
			Views:       views,
			Usages:      usages,
			Description: descText,
		})
	}

	return plan, nil
}

func WriteInstallPlanFile(planFile string, plan *InstallPlan) error {
	// 打开版本文件
	file, err := os.Create(planFile)
	if err != nil {
		return fmt.Errorf("open install plan file `%v' failed: %v", planFile, err)
	}

	defer file.Close()

	return WriteInstallPlan(plan, file)
}

func WriteInstallPlan(plan *InstallPlan, writer io.Writer) error {
	table := make([][4]string, 0, len(plan.items))
	maxlen := [4]int{0, 0, 0, 0}

	for _, item := range plan.items {
		// label
		fieldLabel := item.Lable.String()
		if len(fieldLabel) > maxlen[0] {
			maxlen[0] = len(fieldLabel)
		}

		// views
		views := make([]string, 0, len(item.Views))
		for k := range item.Views {
			views = append(views, k)
		}
		sort.Strings(views)
		fieldViews := strings.Join(views, ":")
		if len(fieldViews) == 0 {
			fieldViews = "-"
		} else {
			fieldViews = ":" + fieldViews + ":"
		}
		if len(fieldViews) > maxlen[1] {
			maxlen[1] = len(fieldViews)
		}

		// usages
		usages := make([]string, 0, len(item.Usages))
		for k := range item.Usages {
			usages = append(usages, k)
		}
		sort.Strings(usages)
		fieldUsages := strings.Join(usages, ":")
		if len(fieldUsages) == 0 {
			fieldUsages = "-"
		} else {
			fieldUsages = ":" + fieldUsages + ":"
		}
		if len(fieldUsages) > maxlen[2] {
			maxlen[2] = len(fieldUsages)
		}

		// comment
		fieldDescription := item.Description
		if len(fieldDescription) == 0 {
			fieldDescription = "#"
		}
		if len(fieldDescription) > maxlen[3] {
			maxlen[3] = len(fieldDescription)
		}

		table = append(table, [4]string{fieldLabel, fieldViews, fieldUsages, fieldDescription})
	}

	// 生成格式化串
	formatLabel := fmt.Sprintf("%%-%dv", maxlen[0])
	formatViews := fmt.Sprintf("%%-%dv", maxlen[1])
	formatUsages := fmt.Sprintf("%%-%dv", maxlen[2])
	formatDescription := "%v\n"
	format := strings.Join([]string{formatLabel, formatViews, formatUsages, formatDescription}, " ")

	fmt.Fprintf(writer, format, "#LABEL", "VIEWS", "USAGES", "DESCRIPTION")

	// 格式化输出
	for _, row := range table {
		fmt.Fprintf(writer, format, row[0], row[1], row[2], row[3])
	}

	return nil
}
